//
//  LGSNode.h
//  LogicSim
//
//  Created by Jeremy Pereira on 04/04/2012.
//  Copyright (c) 2012 Jeremy Pereira. All rights reserved.
//

#import <Foundation/Foundation.h>

/*!
 *  @brief JSON key for the node type.
 *
 *  The node type is the class name of the node.
 */
extern NSString* const LGS_NODE_TYPE_KEY;
extern NSString* const LGS_NAME_KEY;
/*!
 *  @brief JSON key for the label
 */
extern NSString* const LGS_LABEL_KEY;
/*!
 *  @brief JSON key of the inputs
 */
extern NSString* const LGS_INPUTS_KEY;
/*!
 *  @brief JSON key of the outputs
 */
extern NSString* const LGS_OUTPUTS_KEY;
/*!
 *  @brief JSON key for a description of the node type in English.
 */
extern NSString* const LGS_NODE_DESC_KEY;

enum 
{
    LGS_NO_ID = -1,
};

/*!
 *  @brief type for a unit of time.
 *  As this is used to index into NSArrays, it needs to be a compatible type
 *  to NSUInteger.
 */
typedef NSUInteger TimeTick;

@class LGSEdge;
@class LGSConnectionContext;
@class LGSModule;
@class LGSName;

/*!
 *  @brief Base class for all nodes in the circuit's graph.
 *
 *  Implements a prototyping system for creating modified copies of nodes.
 *  @todo Be able to dynamically add new node types
 *  @todo Have a qualified name for the node for hierarchies
 *  @todo Set an initial state at power on
 *  @todo The setters for input and output names should possibly be hidden
 */
@interface LGSNode : NSObject <NSCopying>

/*!
 *  @brief Designated initialiser
 *  @param newName Name the new node will have.
 *  @return A new node.
 */
-(id) initWithName: (LGSName*) newName;

/*!
 *  @brief Convenience initialiser using a string for the name
 *  @param newName NSString containing the new name
 *  @return a new node with the given name
 */
-(id) initWithNameString: (NSString*) newName;

/*!
 *  @brief return a list of descriptors of the supported node types.
 *  
 *  Each descriptor is an array with the following keys:
 *
 *  <table>
 *	<tr><td>desc</td><td>Short English description of the node type</td></tr>
 *	<tr><td>klassName</td><td>name of the class for the node type</td></tr>
 *  </table>
 *  @return array of descriptors.
 */
+(NSArray*) nodeTypeDescriptors;

/*!
 *  @brief Localised description of the node type
 */
@property (nonatomic, copy) NSString* desc;

/*!
 *  @brief produce an object representation capable of being turned into a 
 *  JSON object.
 *  This method is a convenience method that wraps -addJsonKeysToDictionary:
 *  subclasses should override that method rather than this one.
 *  @return a JSONable object - a dictionary in fact
 */
-(NSDictionary*) jsonify;

/*!
 *  @brief given a mutable dictionary, add the keys for this object to it.
 *  Subclasses should overide this method to call the super version then add 
 *  their own keys to the dictionary.
 *  @param dictionary a mutable dictionary to add keys to.
 */
-(void) addJsonKeysToDictionary: (NSMutableDictionary*) dictionary;

/*!
 *  @brief id for the node.
 *  This is a unique id within a context.  It enables nodes to be identified for
 *  the purpose of making connections.  If the node has a module, the module 
 *  will be inforormed of the name change by sending it changeNameForNode:.
 */
@property (nonatomic, copy) LGSName* name;

/*!
 *  @brief a label to know this node by for the purpose of display etc.
 */
@property (nonatomic, copy) NSString* label;

/*!
 *  @brief the gate delay associated with this node
 *  This is the number of ticks a signal takes to propagate through the node.
 */
@property (readonly, assign) TimeTick gateDelay; 

/*!
 *  @brief if a node belongs to a module, this will be the module in question.
 */
@property (weak) LGSModule* module;

/*!
 *  @brief Determines if the node is transient.
 *
 *  If this property is true, the node and its connections will not be 
 *  serialised.  This is useful for nodes that are implied by other things, like 
 *  the input splitters, which are inferred from the input mappings for modules.
 */
@property (assign) bool isTransient;
/*!
 *  @brief evaluate this node.
 *  Evaluates the inputs and applies the node's function and sets the outputs
 *  accordingly.  An AND gate, for instance, will logically AND its inputs and
 *  set its output to the result.  The default implementation throws an 
 *  exception because you can't have a bare node.
 *  @param affectedNodes any nodes connected to the output that are affected by
 *                       the evaluation are added to the set.  If you aren't 
 *                       interested, set this to nil
 */
-(void) evaluateAffectedNodes: (NSMutableOrderedSet*) affectedNodes;

#pragma mark input and output names

// TODO: The setters for input and output names should possibly be hidden.

/*!
 *  @brief Names of inputs to the node.
 *
 *  The array is an array of LGSNames.
 */
@property (nonatomic, strong) NSArray* inputNames;
/*!
 *  @brief Names of ouputs of this node
 *
 *  The array is an array of LGSNames.
 */
@property (nonatomic, strong) NSArray* outputNames;

/*!
 *  @brief Set the input names from an array of strings/
 *
 *  @param strings An array of NSStrings containing the input names.
 */
-(void) setInputNamesFromStrings: (NSArray*) strings;
/*!
 *  @brief Set the output names from an array of strings/
 *
 *  @param strings An array of NSStrings containing the output names.
 */
-(void) setOutputNamesFromStrings: (NSArray*) strings;
/*!
 *  @name Input and output names
 *  Functions for manipulating input and output names.  By default, these do 
 *  not work since most nodes cannot have different input and output names 
 *  from their defaults.
 */
/*! @{ */
/*!
 *  @brief Add a new input name to the node.
 *
 *  This is only allowed if [self canModifyConnections] returns true, otherwise
 *  an exception is thrown.
 *
 *  @param newInputName The name of the new input as an NSString.
 */
-(void) addInputNameString: (NSString*) newInputName;
/*!
 *  @brief Add a new output name to the module.
 *
 *  This is only allowed if [self canModifyConnections] returns true, otherwise
 *  an exception is thrown.
 *
 *  @param newOutputName The name of the new output as an NSString.
 */
-(void) addOutputNameString: (NSString*) newOutputName;

/*!
 *  @brief Rename an input.
 *
 *  If there is a mapping or an external connewction, this will not be lost. If
 *  the node responds to -canModifyConnections with false, this throws an 
 *  exception.  The rename will work only if a connection exists called oldName
 *  and no connection exists called newName.
 *  @param oldName Name of connection to change.
 *  @param newName The new name of the connection.
 *  @return true if there was a connection with oldName and no connection with 
 *  newName.
 */
-(bool) renameInputFromString: (NSString*) oldName
                     toString: (NSString*) newName;
/*!
 *  @brief Rename an output.
 *
 *  If there is a mapping or an external connewction, this will not be lost. If
 *  the node responds to -canModifyConnections with false, this throws an
 *  exception.  The rename will work only if a connection exists called oldName
 *  and no connection exists called newName.
 *  @param oldName Name of connection to change.
 *  @param newName The new name of the connection.
 *  @return true if there was a connection with oldName.
 */
-(bool) renameOutputFromString: (NSString*) oldName
                      toString: (NSString*) newName;

/*!
 *  @brief Delete an input name.
 *
 *  This is only allowed if [self canModifyConnections] returns true, otherwise
 *  an exception is thrown.
 *
 *  @param inputNameToGo The name of the input to delete.
 */
-(void) removeInputNameString: (NSString*) inputNameToGo;
/*!
 *  @brief Delete an output.
 *
 *  This is only allowed if [self canModifyConnections] returns true, otherwise
 *  an exception is thrown.
 *
 *  @param outputNameToGo The name of the new output.
 */
-(void) removeOutputNameString: (NSString*) outputNameToGo;
/*!
 *  @brief Returns true if this node can modify its connections.
 */
@property (readonly, assign) bool canModifyConnections;

/*! @} */

#pragma mark inputs and outputs
/*!
 *  @name Inputs and outputs
 *
 *  Methods for adding and removing inputs and outputs.
 */
/*! @{ */

/*!
 *  @brief inputs to the node.
 */
@property (nonatomic, readonly, strong) NSDictionary *inputs;
/*!
 *  @brief Ouputs of this node
 */
@property (nonatomic, readonly, strong) NSDictionary *outputs;

/*!
 *  @brief The input edge for a given name.
 *  @param inputName Name of the input to get.
 *  @return The input edge or nil if there isn't one for the given name.
 */
-(LGSEdge*) input: (LGSName*) inputName;

/*!
 *  @brief add a new input.
 *  The new input is added to the end of the list of inputs.
 *  @param newInput the new input to add
 *  @param inputName Name of the input to add
 *  @return true if the input was added, false if it breaches a restriction on
 *          the number of inputs or the name of the input.
 */
-(bool) addInput: (LGSEdge*) newInput forName: (LGSName*) inputName;

/*!
 *  @brief Add a new input using a string.
 *
 *  This is a convenience method so that you can use an NSString to add an 
 *  input.
 *  @param newInput The input to add.
 *  @param inputName NSString representing the input name.
 *  @return true if the input was successfully added.
 */
-(bool) addInput: (LGSEdge*) newInput forNameString: (NSString*) inputName;
/*!
 *  @brief add a new output.
 *  The new output is added to the end of the list of outputs.
 *  @param newOutput the new output to add
 *  @param outputName Name of the putput.
 *  @return true if the output was added, false if it breaches a restriction on
 *          the number of outputs.
 */
-(bool) addOutput: (LGSEdge*) newOutput forName: (LGSName*) outputName;
/*!
 *  @brief Add a new output using a string.
 *
 *  This is a convenience method so that you can use an NSString to add an
 *  output.
 *  @param newOutput The output to add.
 *  @param outputName NSString representing the input name.
 *  @return true if the input was successfully added.
 */
-(bool) addOutput: (LGSEdge*) newOutput forNameString: (NSString*) outputName;

/*!
 *  @brief Get the edges connected to a particular output.
 *
 *	@param outputName The name of the output to get the edges for.
 *  @return Set of edges connected to the output or nil if the output doesn't
 *  exist.
 */
-(NSSet*) outputsForName: (LGSName*) outputName;

/*!
 *  @brief Convenience method for connecting two nodes.
 *  @param destination Destination node.
 *  @param outputName Name of the output on this node to connect.
 *  @param inputName Name of the input on the estination node.
 *  @return true if we make the connection, false if the output or the input
 *  does not exist.
 */
-(bool) connect: (LGSNode*) destination
           from: (LGSName*) outputName
             to: (LGSName*) inputName;
/*!
 *  @brief Convenience method for connecting two nodes with strings as 
 *  connection names.
 *  @param destination Destination node.
 *  @param outputName Name of the output on this node to connect as a string.
 *  @param inputName Name of the input on the estination node as a string.
 *  @return true if we make the connection, false if the output or the input
 *  does not exist.
 */
-(bool) connect: (LGSNode*) destination
     fromString: (NSString*) outputName
       toString: (NSString*) inputName;

/*!
 *  @brief removes all inputs
 */
-(void) removeInputs;
/*!
 *  @brief Remove all outputs
 */
-(void) removeOutputs;

/*! @} */

/*!
 *  @brief Override properties with values from the given dictionary.
 *
 *  The supplied dictionary is considered to contain a set of name value pairs,
 *  the names are matched to properties of the receiver and the values are 
 *  used to override the property values.  Each LGSNode subclass must know
 *  which properties can be overridden in this way.  Subclasses should call 
 *  the super implementation before overriding properties themselves.
 *  @param dictionary Dictionary containing key-valu pairs
 *  @param context A context from which module definitions may be drawn
 *  @param error If non nil this will be set to a suitable NSError if something
 *  goes wrong.
 *  @return true if the overrides worked, false if not.
 */
-(bool) overrideWithDictionary: (NSDictionary*) dictionary
                       context: (LGSConnectionContext*) context
                         error: (NSError* __autoreleasing*) error;

/*!
 *  @brief Check if a particular input on this node exists.
 *
 *  @param inputName Name of input to check for.
 *  @return true if the node has an input of that name.
 */
-(bool) hasInput: (LGSName*) inputName;
/*!
 *  @brief Check if a particular output on this node exists.
 *
 *  @param outputName Name of output to check for.
 *  @return true if the node has an output of that name.
 */
-(bool) hasOutput: (LGSName*) outputName;

/*!
 *  @brief Returns true if the node can have subnodes.  
 *  @return true if the node can have subnodes.
 */
@property (readonly, assign) bool canHaveSubNodes;

/*!
 *  @brief The number of subnodes this node has.
 *  @return  The number of subnodes this node has.
 */
-(size_t) subNodeCount;

/*!
 *  @brief find the node for the given name.
 *
 *  If [self canHaveSubNodes] is false, this will throw an exception.
 *
 *  @param name the name to find the node for.  If it has a connection part,
 *  it will be ignored.
 *  @return the subnode with the given label.
 */
-(LGSNode*) nodeForName: (LGSName*) name;

/*!
 *  @brief Add a sub node.
 *
 *  If a node exists with a duplicate label, it will be replaced in the parent,
 *  although their nodes will not have their connections replaced.
 *  Do not add nodes to parents if they are already elsewhere.
 *  If [self canHaveSubNodes] is false, this will throw an exception.
 *  @param newNode node to add to the module
 */
-(void) addNode: (LGSNode*) newNode;

/*!
 *  @brief Remove a subnode.
 *
 *  Remove a node, if it is a subnode of this node.  If it is not a subnode,
 *  do nothing.
 *  In the default case, this does nothing, since most node types do not have 
 *  subnodes.  Node types that do have subnodes will override this to perform
 *  whatever actions are necessary.
 *  @param nodeToGo The node to remove.
 */
-(void) removeNode: (LGSNode*) nodeToGo;

/*!
 *  @brief Returns a list of the names of the subnodes.
 *
 *  The ordering will be lexically according to the locale.
 */
@property (readonly, strong, nonatomic) NSArray* nodeNames;


/*!
 *  @brief Manufacture a new node from an NSDictionary containing node 
 *  attributes.
 *
 *  The "nodeType" key specifies what type of node to create.  This is either
 *  a primitive type (e.g. an LGSAndGate) or the name of an existing node.  
 *  Effectively, this always copies an existing node (pretending there is some
 *  platonic copy of the primitive node types somewhere).  Other keys in the
 *  dictionary override values from the node being copied.
 *
 *  The connection context provides us with the list of already defined nodes.
 *  @param dictionary dictionary of node attributres and keys
 *  @param error if there is an error and this parameter is not nil, an error
 *         object will be returned in it.
 *  @param connectionContext ma
 *  @return the new node or nil if an error occurred.
 *  @todo Re-engineer connection context
 */
+(LGSNode*) makeNodeFromDictionary: (NSDictionary*) dictionary 
                 connectionContext: (LGSConnectionContext*) connectionContext
                             error: (NSError* __autoreleasing *) error;

/*!
 *  @brief Create connections between the nodes in a context.
 *
 *  The context provides the set of nodes to create connections between.  The
 *  dictionary should contain a set of key value pairs that represent the 
 *  mappings between inputs and outputs.  Keys are inputs and values are outputs
 *  in the form of nodeName.inputName or nodeName.outputName
 *  @param connections The dictionary of connections.
 *  @param context Context defining the set of nodes.
 *  @param error This will be set if anything goes wrong.
 *  @return true if all the connections are made, otherwise false.
 */
+(bool) createConnections: (NSDictionary*) connections
			 usingContext: (LGSConnectionContext*) context
                    error: (NSError* __autoreleasing*) error;

/*!
 *  @brief The default input names for the type of node.
 *
 *  This array is used by -init to set the input names of the node when it is 
 *  initialised.  Override this method to set the input names gor the type of
 *  node.
 *  @return The default input names.
 */
+(NSArray*) defaultInputNames;
/*!
 *  @brief The default output names for the type of node.
 *
 *  This array is used by -init to set the output names of the node when it is
 *  initialised.  Override this method to set the output names gor the type of
 *  node.
 *  @return The default output names.
 */
+(NSArray*) defaultOutputNames;

@end
